package queue

import (
	"gitee.com/Billcoding/gotypes"
	"sync"
)

//Define Queue struct
type Queue struct {
	//A sync locker
	mutex sync.Mutex
	//elements
	elements []*gotypes.Element
}

//create new empty Queue
func NewQueue() *Queue {
	elements := make([]*gotypes.Element, 0)
	return &Queue{
		elements: elements,
		mutex:    sync.Mutex{},
	}
}

//clear queue
func (queue *Queue) Clear() {
	queue.elements = make([]*gotypes.Element, 0)
}

//return queue size
func (queue *Queue) Size() int {
	return len(queue.elements)
}

//push element
func (queue *Queue) Push(e gotypes.Element) *Queue {
	queue.mutex.Lock()
	defer queue.mutex.Unlock()
	queue.elements = append(queue.elements, &e)
	return queue
}

//pull first element
func (queue *Queue) Pull() *gotypes.Element {
	var e *gotypes.Element
	queue.mutex.Lock()
	defer queue.mutex.Unlock()
	size := queue.Size()
	if size > 0 {
		e = queue.elements[0]
		es := make([]*gotypes.Element, 0)
		es = append(es, queue.elements[1:]...)
		queue.elements = es
	}
	return e
}

//foreach function
func (queue *Queue) ForEach(fn func(e *gotypes.Element)) {
	for _, e := range queue.elements {
		fn(e)
	}
}

//map function
func (queue *Queue) Map(mapFn func(e *gotypes.Element) gotypes.Element) *Queue {
	return queue.fn(func(e *gotypes.Element) bool {
		return true
	}, mapFn)
}

//filter function
func (queue *Queue) Filter(filterFn func(e *gotypes.Element) bool) *Queue {
	return queue.fn(filterFn, func(e *gotypes.Element) gotypes.Element {
		return *e
	})
}

//reduce function
func (queue *Queue) Reduce(val gotypes.Element, reduceFn func(val gotypes.Element, e *gotypes.Element) gotypes.Element) gotypes.Element {
	firstPtr := &val
	queue.ForEach(func(e *gotypes.Element) {
		*firstPtr = reduceFn(*firstPtr, e)
	})
	return *firstPtr
}

//matchAny function
func (queue *Queue) MatchAny(matchAnyFn func(e *gotypes.Element) bool) bool {
	for _, e := range queue.elements {
		if matchAnyFn(e) {
			return true
		}
	}
	return false
}

//matchNone function
func (queue *Queue) MatchNone(matchNoneFn func(e *gotypes.Element) bool) bool {
	for _, e := range queue.elements {
		if matchNoneFn(e) {
			continue
		}
		return true
	}
	return false
}

//matchAll function
func (queue *Queue) MatchAll(matchAllFn func(e *gotypes.Element) bool) bool {
	count := 0
	for _, e := range queue.elements {
		if matchAllFn(e) {
			count++
		}
	}
	return count == queue.Size()
}

func (queue *Queue) fn(filterFn func(e *gotypes.Element) bool, mapFn func(e *gotypes.Element) gotypes.Element) *Queue {
	newQueue := NewQueue()
	for _, e := range queue.elements {
		val := e
		if filterFn(val) {
			newQueue.Push(mapFn(val))
		}
	}
	return newQueue
}
